<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>ThreadLocalMap</title>
    <style>
        div{
            margin-top:10px;
        }
        html {
            font-size:12px;
        }
        input[type=text] {
            width: 50px;
        }
        input.long {
            width: 100px;
        }
        #desc{
            display: none;
            margin-right: 20px;
            padding-right: 15px;
            background-color:lightyellow;
        }
        span.blue {
            background-color:blue;
            color:white;
            padding:0 3px 0;
            border-radius:100%;
        }
        span.green {
            background-color:green;
            color:white;
            padding:0 3px 0;
            border-radius:100%;
        }
        span.bg{
            background-image: linear-gradient(to right, blue, green);
            color:white;
            padding:0 3px 0;
            border-radius:100%;
        }
        table {
            border-space:0;
            border-collapse: collapse;
        }
        table th {
            border-left: 1px solid black;
            border-top: 1px solid black;
            border-right: 1px solid black;
            padding: 5px;
        }
        table td {
            padding:2px 5px;
            border-left: 1px solid black;
            border-right: 1px solid black;
        }
    </style>
</head>
<body>
<div>
    <label for="capacity">capacity </label><input type="text" id="capacity" value="16" disabled placeholder="请输入容量">
    <label for="factor">factor </label><input type="text" id="factor" value="2/3" disabled placeholder="负载因子">
    <input type="button" value="加一个线程" id="threadBtn">
</div>
<div>
    <table>
        <tr>
            <th colspan="4">计算下一次哈希码</th>
            <th><input type="button" value="GC" id="gcBtn"></th>
        </tr>
        <tr>
            <th></th>
            <th>结果</th>
            <th>桶下标(16)</th>
            <th>桶下标(32)</th>
            <th>要保留的key</th>
        </tr>
        <tr>
            <td><input type="button" value="计算" class="index" id="calBtn"></td>
            <td><input type="text" readonly class="codes long"></td>
            <td><input type="text" readonly class="i16"></td>
            <td><input type="text" readonly class="i32"></td>
            <td>
                <input type="text" id="holdIdx" class="indexes long" placeholder="以,分隔">
            </td>
        </tr>
    </table>
</div>
<div>
    <table id="tbl">
        <thead>
        <tr>
            <th>线程</th>
            <th colspan="2">存入</th>
            <th>删除</th>
            <th>内存结构</th>
            <th>重置</th>
        </tr>
        <tr>
            <th></th>
            <th>key</th>
            <th>固定哈希码</th>
            <th></th>
            <th></th>
            <th></th>
        </tr>
        </thead>
        <tbody>
        <tr id="template" style="display:none">
            <td><span class="tid"></span></td>
            <td><input type="text" class="key"></td>
            <td>
                <input type="text" value="-1" class="fix">
                <input type="button" value="set" class="put">
                <input type="button" value="get" class="get">
            </td>
            <td>
                <input type="button" value="remove" class="remove">
            </td>
            <td>
                <canvas class="canvas" width="300" height="52"></canvas>
            </td>
            <td>
                <input type="button" value="reset" class="reset">
            </td>
        </tr>
        </tbody>
    </table>
</div>
<div id="desc">
    <ol>
        <li>ThreadLocalMap 的容量和负载因子都是固定的，存储于每个 Thread 当中</li>
        <li>key 即为 ThreadLocal，<span class="blue">蓝</span> 表示，value 写死为 v，<span class="green">绿</span> 表示</li>
        <li>ThreadLocal 的 hashCode 与对象无关，是不断累加一个整数产生</li>
        <li>remove index 方法是自己扩展</li>
        <li><span>泄露详情</span>
            <ul>
                <li>gc 时会回收弱引用所指向的对象内存，ThreadLocalMap 中 <span class="bg">entry</span> 是弱引用，但它弱引用指向的仅有 <span class="blue">key</span>，其 <span class="green">value</span> 还是强引用</li>
                <li>gc 有机会让 <span class="blue">key</span> 占用内存被回收；但此时不会清除 <span class="bg">entry</span>，这就是传文中的内存泄露起因</li>
                <li>不过因为使用 <span class="blue">key</span> 时都把它作为共享变量来使用，持有其强引用，因此 gc 连它的内存也不能回收，这时只能通过 remove 回收</li>
                <li>线程如果结束，整个 ThreadLocalMap 会被清空，因此不会泄露</li>
                <li>通常因为线程池会造成线程久久不能结束，此时会增加泄露几率</li>
                <li>正确 remove 就不会泄露，它会把整个 <span class="bg">entry</span> 回收</li>
            </ul>
        </li>
        <li><span>清除算法</span>
            <ul>
                <li>set 会让一部分失效 <span class="bg">entry</span> 被清理</li>
                <li>remove 会让被 remove 的 <span class="blue">key</span> 和其相邻的失效 <span class="bg">entry</span> 被清理</li>
                <li>rehash 会让所有失效 <span class="bg">entry</span> 被清理</li>
            </ul>
        </li>
    </ol>
</div>
<script>
    Array.prototype.shuffle = function() {
      let array = this;
      let len = array.length;
      for (let i = len - 1; i > 0; i--) {
        let j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
      }
      return this
    }
    const colors = [
        {b:"#D24D57", f:"white"},
        {b:"#EB7347", f:"white"},
        {b:"#FC9D99", f:"black"},
        {b:"#26A65B", f:"white"},
        {b:"#AEDD81", f:"black"},
        {b:"#84AF9B", f:"white"},
        {b:"#00CCFF", f:"black"},
        {b:"#D0D0D0", f:"black"},
        {b:"#2C3E50", f:"white"}
    ]
    let idx = 1
    function addRow() {
        let tr = document.getElementById("template").cloneNode(true)
        let parent = document.getElementById("template").parentNode
        tr.removeAttribute("id")
        let tid = `thread-${idx++}`
        tr.querySelector("td span.tid").innerText = tid
        let random = colors.shuffle().pop()
        tr.style.backgroundColor = random.b
        tr.style.color = random.f
        tr.style.display="table-row"
        parent.appendChild(tr)
        let canvas = tr.querySelector(".canvas")
        let url = `http://localhost:8080/threadlocal/addmap?&tid=${tid}`
        fetch(url).then(r=>r.json()).then(json=>{
            loadData(canvas,tid);
        });
    }
    document.getElementById("threadBtn").onclick = function() {
        addRow()
    }
    addRow()
    addRow()
    document.getElementById("gcBtn").onclick = function(){
        let keys = document.getElementById("holdIdx").value
        let url = `http://localhost:8080/threadlocal/gc?keys=${keys}`;
        fetch(url).then(r=>r.json()).then(json=>{
            let trs = [...document.getElementById("tbl").querySelectorAll("tbody tr")]
            for(let tr of trs) {
                let tid = tr.querySelector('span.tid').innerText
                let canvas = tr.querySelector(".canvas")
                if(tid){
                    loadData(canvas,tid)
                }
            }
        });
    }
    document.getElementById("calBtn").onclick = function(){
        let tr = this.parentNode.parentNode;
        let url = `http://localhost:8080/threadlocal/index`;
        fetch(url).then(r=>r.json()).then(json=>{
            tr.querySelector(".codes").value=json.code;
            tr.querySelector(".i16").value = (16 - 1) & json.code;
            tr.querySelector(".i32").value = (32 - 1) & json.code;
        });
    }
    document.querySelector("#tbl tbody").addEventListener("click", function(e){
        e.stopPropagation();
        let type = e.target.className
        let tr = e.target.parentNode.parentNode
        let tid = tr.querySelector('span.tid').innerText
        let canvas = tr.querySelector(".canvas")
        if(type === 'remove'){
            let key = tr.querySelector('.key').value;
            let url = `http://localhost:8080/threadlocal/remove?key=${key}&tid=${tid}`;
            fetch(url).then(r=>r.json()).then(json=>{
                loadData(canvas,tid);
            });
        } else if(type === 'put') {
            let key = tr.querySelector('.key').value;
            let fix = tr.querySelector('.fix').value;
            let url = `http://localhost:8080/threadlocal/put?key=${key}&fix=${fix}&tid=${tid}`;
            fetch(url).then(r=>r.json()).then(json=>{
                loadData(canvas,tid);
            });
        } else if(type === 'reset') {
            let url = `http://localhost:8080/threadlocal/reset?tid=${tid}`;
            fetch(url).then(r=>r.json()).then(json=>{
                loadData(canvas,tid);
            });
        } else if(type === 'get') {
            let key = tr.querySelector('.key').value;
            let fix = tr.querySelector('.fix').value;
            let url = `http://localhost:8080/threadlocal/get?key=${key}&fix=${fix}&tid=${tid}`;
            fetch(url).then(r=>r.json()).then(json=>{
                loadData(canvas,tid);
            });
        }
    })

    function drawBuckets(canvas,buckets,bWidth,bHeight,xBegin,yBegin){
        let ctx = canvas.getContext("2d");
        let length = buckets.length;
        let tableCells = [];
        for(let i = 0; i < length; i++) {
            let x = xBegin + i*(bWidth + 2);
            let y = yBegin ;
            tableCells.push({x, yBegin});
            ctx.fillStyle="yellow";
            ctx.fillRect(x,yBegin,bWidth,bHeight);

            ctx.fillStyle="black";
            //ctx.font="14px Georgia";
            ctx.font = "bold 12px Arial";
            ctx.fillText(i,x+bWidth/2-5,y+bHeight/2+4);
        }

        for(let i = 0; i < length; i++) {
            let x = tableCells[i].x;
            let entry = buckets[i];
            if(entry) {
                let y = yBegin + bHeight + 2;
                ctx.beginPath();
                ctx.fillStyle=entry.key!=null?"blue":"gray";
                ctx.arc(x+pointSize, y+pointSize, pointSize, (Math.PI / 180) * 0, (Math.PI / 180) * 360, false);
                ctx.fill();
                ctx.closePath();
                ctx.fillStyle = "white";
                ctx.font = "bold 10px Arial";
                ctx.fillText(entry.key!=null?entry.key:'',x+bWidth/2-5,y+bHeight/2+4);

                y+= bHeight + 2;
                ctx.beginPath();
                ctx.fillStyle=entry.value!=null?"green":"gray";
                ctx.arc(x+pointSize, y+pointSize, pointSize, (Math.PI / 180) * 0, (Math.PI / 180) * 360, false);
                ctx.fill();
                ctx.closePath();
                ctx.fillStyle = "white";
                ctx.font = "bold 10px Arial";
                ctx.fillText(entry.value!=null?entry.value:'',x+bWidth/2-5,y+bHeight/2+4);
            }
        }
    }
    function loadData(canvas,tid) {
        fetch(`http://localhost:8080/threadlocal/map?tid=${tid}`).then(r=>r.json()).then(data=>{
            const heightUnit = 100;
            canvas.width = 640;
            drawBuckets(canvas,data.buckets, 18,15,0,0);
        });
    }

    var pointSize = 8;


</script>
</body>
</html>